﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

/* contains the rom data as a byte array alongside the header data
 * Gareth Williams
 */

namespace RomHead.model
{
    public class SnesRom
    {
        public    
            enum MapperTypes { LoROM, HiROM };

        #region properties

        private MapperTypes _mapper;
        public MapperTypes mapper
        {
            get
            {
                return _mapper;
            }
            set
            {
                _mapper = value;
                extractHeader(_romData);
            }
        }
        private bool _isHeader;
        public bool isHeader
        {
            get 
            {
                return _isHeader;
            }
            set
            {
                _isHeader = value;
                extractHeader(_romData);
            }
        }

        private SnesHeaderData _headerData;
        public SnesHeaderData headerData 
        {
            get //read only
            {
                return _headerData;
            }
        }

        private byte[] _romData;
        public byte[] romData
        {
            get
            {
                return _romData;
            }
            set
            {
                _romData = value;
                AttemptToFindHeaderOffset();
                extractHeader(_romData);
            }
        }

        #endregion properties;

        public void Clear()
        {
            _headerData.Clear();

            _romData = null;
            _mapper = MapperTypes.HiROM;
            _isHeader = true;
        }

        #region helpers
        
        //http://romhack.wikia.com/wiki/SNES_header
        private void extractHeader(byte[] fromRomData)
        {
            if (_romData == null) return;

            if(_headerData != null) _headerData.Clear();
            else _headerData = new SnesHeaderData();

            _headerData.name            = extractTitle(fromRomData);
            _headerData.layout          = extractLayout(fromRomData);
            _headerData.type            = extractType(fromRomData);
            _headerData.romSize         = extractRomSize(fromRomData);
            _headerData.ramSize         = extractRamSize(fromRomData);
            _headerData.countryCode     = extractCountryCode(fromRomData);
            _headerData.licenceCode     = extractLicencee(fromRomData);
            _headerData.versionNumber   = extractVersion(fromRomData);
        }
        private string extractTitle(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffc0);
            int bytesToRead = 21;
            string title;
            System.Text.Encoding enc = System.Text.Encoding.ASCII;
            title = enc.GetString(fromRomData, (int)snesOffset, bytesToRead);

            return title;
        }
        private uint extractLayout(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffd5);
            //Debug.WriteLine("snesOffset:{0:X} {0:D} {1:D}", snesOffset, (int)snesOffset);
            return fromRomData[(int)snesOffset];
        }
        private uint extractType(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffd6);
            return fromRomData[(int)snesOffset];
        }
        private uint extractRomSize(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffd7);
            return fromRomData[(int)snesOffset];
        }
        private uint extractRamSize(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffd8);
            return fromRomData[(int)snesOffset];
        }
        private uint extractCountryCode(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffd9);
            return fromRomData[(int)snesOffset];
        }
        private uint extractLicencee(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffda);
            return fromRomData[(int)snesOffset];
        }
        private uint extractVersion(byte[] fromRomData)
        {
            uint snesOffset = SnesToFileAddress(0xffdb);
            return fromRomData[(int)snesOffset];
        }

        //http://romhack.wikia.com/wiki/SNES_ROM_layout#Formulas_to_convert_addresses

        private uint fileToSnesAddress(uint addr)
        {
            if (isHeader) addr -= 512;

            switch (mapper)
            {
                case MapperTypes.LoROM:
                    {
                        addr = ((addr & 0x7f8000) << 1) + 0x8000 + (addr & 0x7fff);
                    } break;

                case MapperTypes.HiROM:
                    {
                        addr = 0xc00000 + (addr & 0x3fffff);
                    } break;
            }

            return addr;
        }
        private uint SnesToFileAddress(uint addr)
        {
            switch (mapper)
            {
                case MapperTypes.LoROM:
                    {
                        addr = ((addr & 0x7f0000) >> 1) + (addr & 0x7fff);
                    } break;

                case MapperTypes.HiROM:
                    {
                        addr = addr & 0x3fffff;
                    } break;
            }

            if (isHeader) addr += 512;

            return addr;
        }

        private void AttemptToFindHeaderOffset()
        {
            mapper = MapperTypes.LoROM;
            isHeader = false;
            if (containsAlphaNumericCharacters(headerData.name))
            {
                Debug.WriteLine("LoROM false");
                return;
            }
            mapper = MapperTypes.LoROM;
            isHeader = true;
            if (containsAlphaNumericCharacters(headerData.name))
            {
                Debug.WriteLine("LoROM true");
                return;
            }

            mapper = MapperTypes.HiROM;
            isHeader = false;
            if (containsAlphaNumericCharacters(headerData.name))
            {
                Debug.WriteLine("HiROM false");
                return;
            }

            mapper = MapperTypes.HiROM;
            isHeader = true;
            if (containsAlphaNumericCharacters(headerData.name))
            {
                Debug.WriteLine("HiROM true");
                return;
            }
        }
        private bool containsAlphaNumericCharacters(string s)
        {
            string matchAlphaNumericAndWhiteSpace = "^[a-zA-Z0-9 ]*$";
            return System.Text.RegularExpressions.Regex.IsMatch(s, 
                matchAlphaNumericAndWhiteSpace, 
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        }
        #endregion helpers
    }
}
